// $Id: output_cpp.cpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#include <cstdlib>
#include <ostream>
#include <set>
#include <boost/assert.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <xpgen/output_cpp.hpp>
#include <xpgen/model.hpp>
#include <xpgen/ns.hpp>
#include "names.hpp"

using namespace std;
namespace dt = boost::posix_time;

namespace xpgen {

namespace {

const char* const cpp_keywords[] = {
    "bool", "break", "case", "catch", "char", "class", "const",
    "const_cast", "continue", "default", "delete", "do", "double",
    "dynamic_cast", "else", "enum", "explicit", "extern", "false",
    "float", "for", "friend", "goto", "if", "inline", "int", "long",
    "mutable", "namespace", "new", "operator", "private", "protected",
    "public", "register", "reinterpret_cast", "return", "short",
    "signed", "sizeof", "static", "static_cast", "struct", "switch",
    "template", "this", "throw", "true", "try", "typedef", "typeid",
    "typename", "union", "unsigned", "using", "virtual", "void",
    "volatile", "wchar_t", "while"
#if 0
    // our pseudo keyword
    ,
    N_base
#endif
};
set<string> cpp_keyword_set;

} // anonymous namespace

void output_cpp::init()
{
    BOOST_FOREACH(const char* keyword, cpp_keywords)
    {
        cpp_keyword_set.insert(keyword);
    }
}

void output_cpp::finish()
{
    cpp_keyword_set.clear();
}

output_cpp::output_cpp(output* prev) :
    output(prev),
    upper_constant(false),
    upper_method(false),
    write_scriptable(false)
{
    output_ext = "hpp"; // default
    native_types.resize(type_decl::tag_max);
}

output_cpp::~output_cpp()
{
}

void output_cpp::do_reset()
{
    output::do_reset();

    output_cpp* p = dynamic_cast<output_cpp*>(prev());
    if (p) {
        native_types = p->native_types;
        upper_constant = p->upper_constant;
        upper_method = p->upper_method;
        write_scriptable = p->write_scriptable;

    } else {
        // Check for native types
        // Also add "bool" to it
        for (unsigned tag = 0; tag < native_types.size(); ++tag) {
            type_decl type;
            type.tag = tag;

            if (!(type.is_standard() ||
                  type.is_pstring() ||
                  type.tag == type_decl::tag_size)) {
                native_types[tag].clear();
            }
        }
        native_types[type_decl::tag_bool] = N_bool;
    }
}

void output_cpp::print_file_header(
        ostream& os, const string& out_filename) const
{
    dt::ptime pt = dt::second_clock::local_time();
    os << indent() << "// " << out_filename << endl
       << indent() << "// " << "Generated from "
                   << input_file.file_string()
                   << ", " << pt << endl
       << indent() << "// *** Do NOT edit! ***" << endl;
}

void output_cpp::print_includes(ostream& os, unsigned inc_flags) const
{
    static const struct
    {
        unsigned flag;
        const char* header;
    }
    includes[] =
    {
        { inc_array,                    N_INC_array                 },
        { inc_array_fwd,                N_INC_array_fwd             },
        { inc_assert,                   N_INC_assert                },
        { inc_exception,                N_INC_exception             },
        { inc_interface,                N_INC_interface             },
        { inc_ptr,                      N_INC_ptr                   },
        { inc_ptr_array,                N_INC_ptr_array             },
        { inc_result_code,              N_INC_result_code           },
        { inc_string,                   N_INC_string                },
        { inc_ustring,                  N_INC_ustring               },
        { inc_xpcom,                    N_INC_xpcom                 },
        { inc_xpcom_impl,               N_INC_xpcom_impl            },
        { inc_proxy_array,              N_INC_proxy_array           },
        { inc_proxy_bool,               N_INC_proxy_bool            },
        { inc_proxy_interface_proxy,    N_INC_proxy_interface_proxy },
        { inc_proxy_pstring,            N_INC_proxy_pstring         },
        { inc_proxy_ptr,                N_INC_proxy_ptr             },
        { inc_proxy_uuid_ptr,           N_INC_proxy_uuid_ptr        },
        { inc_impl_array,               N_INC_impl_array            },
        { inc_impl_bool,                N_INC_impl_bool             },
        { inc_impl_exception,           N_INC_impl_exception        },
        { inc_impl_interface_impl,      N_INC_impl_interface_impl   },
        { inc_impl_pstring,             N_INC_impl_pstring          },
        { inc_impl_ptr,                 N_INC_impl_ptr              },
        { inc_impl_ptr_array,           N_INC_impl_ptr_array        },
        { inc_impl_uuid_ptr,            N_INC_impl_uuid_ptr         }
    };

    // Adjustment
    if (inc_flags & inc_xpcom_impl)
        inc_flags = inc_xpcom_impl;
    if (inc_flags & inc_xpcom)
        inc_flags = inc_xpcom;
    if (inc_flags & (inc_array | inc_ptr_array)) {
        inc_flags &= ~inc_array_fwd;
    }

    for (size_t i = 0; i < sizeof(includes) / sizeof(includes[0]); ++i) {
        if (inc_flags & includes[i].flag)
            os << indent() << "#include " << includes[i].header << endl;
    }
}

void output_cpp::print_ns_begin(ostream& os, const string& ns_name)
{
    os << indent() << "namespace " << ns_name << " {" << endl;
    push_ns(ns_name);
}

void output_cpp::print_ns_end(ostream& os)
{
    os << indent() << "} // namespace " << ns::get_ns_cpp(ns_stack()) << endl;
    pop_ns();
}

void output_cpp::print_interface_ns_begin(ostream& os, interface_decl* itf)
{
    for (size_t i = 0; i < itf->ns_list.size(); ++i) {
        print_ns_begin(os, itf->ns_list[i]);
    }
}

void output_cpp::print_interface_ns_end(ostream& os, interface_decl* itf)
{
    for (size_t i = itf->ns_list.size(); i > 0; --i) {
        print_ns_end(os);
    }
}

void output_cpp::print_interface_forward(ostream& os, interface_decl* itf)
{
    interface_decl* prev = 0;
    BOOST_FOREACH(interface_decl* current, itf->references)
    {
        if (!prev) {
            os << endl;
            print_interface_ns_begin(os, current);
        } else {
            if (current->ns != prev->ns) {
                print_interface_ns_end(os, prev);
                print_interface_ns_begin(os, current);
            }
        }
        prev = current;
        os << indent() << "class " << current->name << ";" << endl;
    }
    if (prev)
        print_interface_ns_end(os, prev);
}

void output_cpp::print_method_signature(
        ostream& os, method_decl* method, unsigned flags)
{
    if (write_scriptable && method->is_scriptable()) {
        os << N_XPCOM_SCRIPTABLE << " ";
    }

    const char* p_N_XPCOM_IMETHOD =
            (flags & method_impl) ? N_XPCOM_IMETHODIMP : N_XPCOM_IMETHOD;
    const char* p_N_XPCOM_IMETHOD_ =
            (flags & method_impl) ? N_XPCOM_IMETHODIMP_ : N_XPCOM_IMETHOD_;
    if (method->is_notxpcom()) {
        os << p_N_XPCOM_IMETHOD_
           << "(" << get_param_type_name(method->result) << ") ";
    } else {
        os << p_N_XPCOM_IMETHOD << " ";
    }

    os << get_method_name_cpp(method, (flags & method_proxied) != 0) << "(";

    for (size_t i = 0; i < method->params.size(); ++i) {
        if (i != 0)
            os << ", ";

        param_decl* param = method->params[i];
        os << get_param_type_name(param);
        os << " " << param->name;
    }

    if (method->is_result_retval_param()) {
        if (!method->params.empty())
            os << ", ";
        os << get_param_type_name(method->result);
        os << " " << method->result->name;
    }

    os << ")";
}

string output_cpp::get_relative_ns_cpp(const string& ns) const
{
    vector<string> ns_list;
    ns::get_ns_list(ns_list, ns);
    return get_relative_ns_cpp(ns_list);
}

string output_cpp::get_relative_ns_cpp(const vector<string>& ns_list) const
{
    return ns::get_relative_ns_cpp(ns_list, ns_stack());
}

string output_cpp::get_relative_interface_qname_cpp(interface_decl* itf) const
{
    return ns::get_qname_cpp(get_relative_ns_cpp(itf->ns_list), itf->name);
}

string output_cpp::get_relative_xpcom_qname_cpp(const string& name) const
{
    return ns::get_qname_cpp(get_relative_ns_cpp(N_xpcom), name);
}

string output_cpp::get_include_guard_name(interface_decl* itf, const string& suffix) const
{
    return get_include_guard_name(get_include_guard_basename(itf), suffix);
}

string output_cpp::get_include_guard_name(const string& basename, const string& suffix) const
{
    string ret;

    ret += "__" N_xpcomplus "_";
    ret += basename;
    if (!suffix.empty())
        ret += suffix;
    ret += "_";
    ret += output_ext;
    ret += "__";

    return ret;
}

string output_cpp::get_include_guard_basename(interface_decl* itf)
{
    string ret;

    for (size_t i = 0; i < itf->ns_list.size(); ++i) {
        ret += itf->ns_list[i];
        ret += "_";
    }
    ret += itf->name;

    return ret;
}

string output_cpp::get_name_cpp(const string& name)
{
    string ret = name;
    if (cpp_keyword_set.find(name) != cpp_keyword_set.end())
        ret += '_';
    return ret;
}

string output_cpp::get_constant_name_cpp(constant_decl* constant) const
{
    string ret = constant->name;

    if (upper_constant) {
        BOOST_FOREACH(char& c, ret)
        {
            c = toupper(c);
        }
    }
    ret = get_name_cpp(ret);

    return ret;
}

string output_cpp::get_method_name_cpp(method_decl* method, bool proxied) const
{
    string ret;

    if (proxied) {
        string name = method->name;
        if (upper_method) {
            name[0] = toupper(name[0]);
        }

        if (method->is_getter())
            ret = N_PREFIX_get;
        else if (method->is_setter())
            ret = N_PREFIX_set;
        else
            ret = N_PREFIX_raw;

        ret += "_";
        ret += name;

    } else {
        if (method->is_attribute()) {
            // Make first letter uppercase
            string name = method->name;
            name[0] = toupper(name[0]);

            if (method->is_getter())
                ret = N_PREFIX_get;
            else
                ret = N_PREFIX_set;
            ret += name;

        } else {
            ret = method->name;
        }

        if (upper_method) {
            ret[0] = toupper(ret[0]);
        }
    }

    ret = get_name_cpp(ret);
    return ret;
}

string output_cpp::get_proxy_method_name_cpp(method_decl* method) const
{
    return get_method_name_cpp(method, false);
}

string output_cpp::get_param_type_name(param_decl* param) const
{
    string ret;

    // Decide to print "const"
    if (param->is_in() && (
        param->is_const() || param->type.is_uuid() ||
        param->type.is_string() || param->type.is_string_ptr() ||
        param->type.is_pstring()))
    {
        ret += "const ";
    } else if (param->is_shared()) { // in and shared should not be together
        ret += "const ";
    }

    ret += get_type_basename(&param->type);

    // Certain types need tuning
    if (param->type.tag == type_decl::tag_interface)
        ret += "*";
    else if (param->type.is_string())
        ret += "&";
    else if (param->type.is_string_ptr())
        ret += "*";

    // out param receives *
    if (param->has_out() && !param->type.is_dipper())
        ret += "*";

    // Array receives another *
    if (param->type.is_array)
        ret += "*";

    return ret;
}

string output_cpp::get_proxy_param_type_name(param_decl* param) const
{
    string ret;

    // Decide to print "const"
    if (param->is_in() && (
        param->is_const() || param->type.is_array || param->type.is_uuid() ||
        param->type.is_string() || param->type.is_string_ptr() ||
        param->type.is_pstring()))
    {
        ret += "const ";
    } else if (param->is_shared()) { // in and shared should not be together
        ret += "const ";
    }

    // Some type needs change
    type_decl type = param->type;

    // interface_qi/interface_qi_out map to interface*
    if (type.tag == type_decl::tag_interface_qi ||
        type.tag == type_decl::tag_interface_qi_out)
    {
        type.tag = type_decl::tag_interface;
        type.itf = module()->root_interface();
    }
    // pstring type for [out], [inout] and not [shared] map to string type
    else if (type.is_pstring() && !type.is_array) {
        if (param->has_out() && !param->is_shared()) {
            if (type.tag == type_decl::tag_pstring)
                type.tag = type_decl::tag_string;
            else
                type.tag = type_decl::tag_wstring;
        }
    }
    // uuid_ptr map to uuid if is array or [out], [inout]
    else if (type.tag == type_decl::tag_uuid_ptr) {
#if 1
        type.tag = (type.is_array || param->has_out())
                ? type_decl::tag_uuid : type_decl::tag_uuid_ref;
#else
        if (type.is_array || param->has_out())
            type.tag = type_decl::tag_uuid;
#endif
    }

    // Get type basename
    string basename = get_proxy_type_basename(&type);
    bool is_reference = type.is_dipper();

    if (type.is_array) { // array
        if (type.tag == type_decl::tag_interface)
            ret += get_relative_xpcom_qname_cpp(N_ptr_array);
        else
            ret += get_relative_xpcom_qname_cpp(N_array);
        ret += "<";
        ret += basename;
        ret += ">";

        is_reference = true;

    } else if (type.tag == type_decl::tag_interface) { // interface
        if (param->is_in())
            ret += get_relative_xpcom_qname_cpp(N_noref_ptr);
        else {
            ret += get_relative_xpcom_qname_cpp(N_ref_ptr);
            is_reference = true;
        }
        ret += "<";
        ret += basename;
        ret += ">";

    } else {
        ret += basename;
    }

    if (!param->is_retval()) {
        if (is_reference)
            ret += "&";
        else if (param->has_out())
            ret += "&";
    }

    return ret;
}

string output_cpp::get_proxy_array_type_basename(type_decl* type_) const
{
    type_decl type = *type_;
    BOOST_ASSERT(type.is_array && "must be array type!");

    // XXX Is it really needed for interface_qi/interface_qi_out?

    // interface_qi/interface_qi_out map to interface*
    if (type.tag == type_decl::tag_interface_qi ||
        type.tag == type_decl::tag_interface_qi_out)
    {
        type.tag = type_decl::tag_interface;
        type.itf = module()->root_interface();
    } else if (type.tag == type_decl::tag_uuid_ptr) {
        type.tag = type_decl::tag_uuid;
    }

    return get_proxy_type_basename(&type);
}

string output_cpp::get_type_basename(type_decl* type) const
{
    static const char* const cpp_type_str[] = {
        N_void,     N_int8_t,   N_int16_t,  N_int32_t,  N_int64_t,
        N_uint8_t,  N_uint16_t, N_uint32_t, N_uint64_t, N_float,
        N_double,   N_bool_t,   N_char,     N_wchar,    N_uuid,
        N_uuid"&",  N_uuid"*",  N_string,   N_wstring,  N_ustring,
        N_wstring,  N_string,   N_wstring,  N_ustring,  N_wstring,
        N_char"*",  N_wchar"*", 0,          N_void"*",  N_void"*",
        N_void"*",  N_result_t, N_size_t
    };

    static const bool is_type_xpcom[] = {
        false,      true,       true,       true,       true,
        true,       true,       true,       true,       false,
        false,      true,       false,      true,       true,
        true,       true,       true,       true,       true,
        true,       true,       true,       true,       true,
        false,      true,       false,      false,      false,
        false,      true,       true
    };

    string ret;

    if (type->tag == type_decl::tag_interface) {
        ret = get_relative_interface_qname_cpp(type->itf);
    } else {
        const char* s = cpp_type_str[type->tag];
        BOOST_ASSERT(s);

        if (is_type_xpcom[type->tag])
            ret = get_relative_xpcom_qname_cpp(s);
        else
            ret = s;
    }

    return ret;
}

string output_cpp::get_proxy_type_basename(type_decl* type) const
{
    return native_types[type->tag].empty()
            ? get_type_basename(type) : native_types[type->tag];
}

string output_cpp::get_param_in_out_str(param_decl* param)
{
    string ret;

    if (param->is_in())
        ret = N_in;
    else if (param->is_out())
        ret = N_out;
    else if (param->is_inout())
        ret = N_inout;
    else
        BOOST_ASSERT(false && "param has no [in, out, inout] specified");

    return ret;
}

string output_cpp::get_param_size_length_str(param_decl* param)
{
    string ret;

    if (param->is_size_is())
        ret = N_size;
    else if (param->is_length_is())
        ret = N_length;
    else if (param->is_size_length_is())
        ret = N_size2;
    else
        BOOST_ASSERT(false && "param has no [size_is, length_is] specified");

    return ret;
}

bool output_cpp::is_param_pstring_with_size(param_decl* param)
{
    return param->type.is_pstring() &&
        !param->type.is_array && param->is_primary();
}

bool output_cpp::is_proxy_param_primary(param_decl* param)
{
    // input pstring is not considered primary
    bool ret = param->is_primary();
    if (ret && param->is_in() && is_param_pstring_with_size(param))
        ret = false;
    return ret;
}

bool output_cpp::is_proxy_param_sub(param_decl* param)
{
    bool ret = param->is_sub();
    if (ret) {
        param_decl* primary = param->primary();
        ret = is_proxy_param_primary(primary);

        // XXX Special for output array|pstring with input [size_is]
        if (is_param_out_isize2(primary))
            ret = false;
    }
    return ret;
}

bool output_cpp::is_param_out_isize2(param_decl* param)
{
    if (param->is_primary() &&
        (param->type.is_array || param->type.is_pstring()))
    {
        if (param->size_is == param->length_is &&
            param->is_out() && param->size_is->is_in())
        {
            return true;
        }
    }
    return false;
}

void output_cpp::check_primary_param(param_decl* param)
{
    bool ok = true;

    if (param->is_primary() &&
        (param->type.is_array || param->type.is_pstring()))
    {
        if (param->in_out_flag() != param->size_is->in_out_flag() ||
            param->in_out_flag() != param->length_is->in_out_flag())
        {
            if (!is_param_out_isize2(param))
                ok = false;
        }
    }

    if (!ok) {
        ostringstream s;
        s << "mismatched [in out] flags of primary and sub param detected ("
          << param->context_name()
          << "), please report the IDL for further support.";
        throw runtime_error(s.str());
    }
}

void output_cpp::check_sub_param(param_decl* param)
{
    bool ok = true;

    if (param->is_sub() && param->primary_count > 1 && !param->is_iid_is()) {
        if (!param->is_in() || param->primary_count > 3) {
            ok = false;
        } else {
            for (unsigned i = 0; i < param->primary_count; ++i) {
                param_decl* primary = param->primary(i);
                if (!primary->type.is_array ||
                    !primary->is_in() ||
                    primary->size_is != primary->length_is)
                {
                    ok = false;
                    break;
                }
            }
        }
    }

    if (!ok) {
        ostringstream s;
        s << "multiple primary param is supported only if all the primary"
          << " params are array, and all params are [in], and count of primary"
          << " params <= 3 ("
          << param->context_name()
          << "), please report the IDL for further support.";
        throw runtime_error(s.str());
    }
}

} // namespace xpgen
